
// ===============================================================================================================
// -*- C++ -*-
//
// Doom 3 MD5.cpp - Classes and data structures to support loading and use of the Doom 3 MD5 models.
//
// Copyright (c) 2005-2007 David Henry
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// Modified by: Guilherme R. Lampert - March 2011
// guilherme.ronaldo.lampert@gmail.com
//
// ===============================================================================================================

#include <Doom 3 MD5.hpp>

// ANSI C string utilities
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// =========================================================
// DoomMD5Anim Class Implementation
// =========================================================

DoomMD5Anim::DoomMD5Anim(MemoryBuffer * memory)
: ReferenceCountable(), skelFrames(0), bboxes(0), numFrames(0), numJoints(0), frameRate(0), currFrame(0), nextFrame(1), lastTime(0), maxTime(0)
{
	// Load an MD5 animation from a file in memory.

	JointInfo * jointInfos = 0;
	BaseFrameJoint * baseFrame = 0;
	float * animFrameData = 0;

	int version, i;
	int frameIndex;
	int numAnimatedComponents;
	char buff[1024];

	// Validation of the memory buffer is done outside.

	while (!memory->EndOfStream())
	{
		// Read whole line:
		memory->ReadString(buff, sizeof(buff));

		if (sscanf(buff, "MD5Version %d", &version) == 1)
		{
			if (version != 10)
			{
				// Bad version!
				LOG_ERROR("Bad animation version!");
				return;
			}
		}
		else if (sscanf(buff, " numFrames %d", &numFrames) == 1)
		{
			// Allocate memory for skeleton frames and bounding boxes:
			if (numFrames > 0)
			{
				skelFrames = new DoomMD5Joint *[numFrames];
				bboxes = new BoundingBox[numFrames];
			}
		}
		else if (sscanf(buff, " numJoints %d", &numJoints) == 1)
		{
			if (numJoints > 0)
			{
				for (i = 0; i < numFrames; ++i)
				{
					// Allocate memory for joints of each frame:
					skelFrames[i] = new DoomMD5Joint[numJoints];
				}

				// Allocate temporary memory for building skeleton frames:
				jointInfos = new JointInfo[numJoints];
				baseFrame = new BaseFrameJoint[numJoints];
			}
		}
		else if (sscanf(buff, " frameRate %d", &frameRate) == 1)
		{
			maxTime = 1.0f / frameRate; // Set maxTime
		}
		else if (sscanf(buff, " numAnimatedComponents %d", &numAnimatedComponents) == 1)
		{
			if (numAnimatedComponents > 0)
			{
				// Allocate memory for animation frame data:
				animFrameData = new float[numAnimatedComponents];
			}
		}
		else if (strncmp(buff, "hierarchy {", 11) == 0)
		{
			for (i = 0; i < numJoints; ++i)
			{
				// Read whole line:
				memory->ReadString(buff, sizeof(buff));

				// Read joint info:
				sscanf(buff, " %s %d %d %d", jointInfos[i].name, &jointInfos[i].parent, &jointInfos[i].flags, &jointInfos[i].startIndex);
			}
		}
		else if (strncmp(buff, "bounds {", 8) == 0)
		{
			for (i = 0; i < numFrames; ++i)
			{
				// Read whole line:
				memory->ReadString(buff, sizeof(buff));

				// Read bounding box:
				sscanf(buff, " ( %f %f %f ) ( %f %f %f )",
				&bboxes[i].min.x, &bboxes[i].min.y, &bboxes[i].min.z,
				&bboxes[i].max.x, &bboxes[i].max.y, &bboxes[i].max.z);
			}
		}
		else if (strncmp(buff, "baseframe {", 10) == 0)
		{
			for (i = 0; i < numJoints; ++i)
			{
				// Read whole line:
				memory->ReadString(buff, sizeof(buff));

				// Read base frame joint:
				if (sscanf(buff, " ( %f %f %f ) ( %f %f %f )",
				&baseFrame[i].pos.x, &baseFrame[i].pos.y, &baseFrame[i].pos.z,
				&baseFrame[i].orient.x, &baseFrame[i].orient.y, &baseFrame[i].orient.z) == 6)
				{
					// Compute the w component of the quaternion:
					baseFrame[i].orient.ComputeW();
				}
			}
		}
		else if (sscanf(buff, "frame %d {", &frameIndex) == 1)
		{
			// Read frame data:
			for (i = 0; i < numAnimatedComponents;)
			{
				memory->ReadString(buff, sizeof(buff));
				char * tok = strtok(buff, " \t\r\n"); // Split the string by white spaces, tabs and line terminators.

				while (tok != 0)
				{
					animFrameData[i++] = static_cast<float>(strtod(tok, 0));
					tok = strtok(0, " \t\r\n");
				}
			}

			// Build frame skeleton from the collected data:
			BuildFrameSkeleton(jointInfos, baseFrame, animFrameData, skelFrames[frameIndex], numJoints);
		}
	}

	// Free temporary data that was allocated:
	delete[] jointInfos;
	delete[] baseFrame;
	delete[] animFrameData;
}

DoomMD5Anim::~DoomMD5Anim(void)
{
	// Free resources allocated for the animation.

	if (skelFrames)
	{
		for (int i = 0; i < numFrames; ++i)
		{
			delete[] skelFrames[i];
		}

		delete[] skelFrames;
	}

	delete[] bboxes;
}

void DoomMD5Anim::BuildFrameSkeleton(const JointInfo * jointInfos, const BaseFrameJoint * baseFrame,
									 const float * animFrameData, DoomMD5Joint * skelFrame, int numberOfJoints)
{
	for (int i = 0; i < numberOfJoints; ++i)
	{
		const BaseFrameJoint * baseJoint = &baseFrame[i];
		Vec3 animatedPos = baseJoint->pos;
		Quaternion animatedOrient = baseJoint->orient;
		int j = 0;

		if (jointInfos[i].flags & 1) /* Tx */
		{
			animatedPos.x = animFrameData[jointInfos[i].startIndex + j];
			++j;
		}

		if (jointInfos[i].flags & 2) /* Ty */
		{
			animatedPos.y = animFrameData[jointInfos[i].startIndex + j];
			++j;
		}

		if (jointInfos[i].flags & 4) /* Tz */
		{
			animatedPos.z = animFrameData[jointInfos[i].startIndex + j];
			++j;
		}

		if (jointInfos[i].flags & 8) /* Qx */
		{
			animatedOrient.x = animFrameData[jointInfos[i].startIndex + j];
			++j;
		}

		if (jointInfos[i].flags & 16) /* Qy */
		{
			animatedOrient.y = animFrameData[jointInfos[i].startIndex + j];
			++j;
		}

		if (jointInfos[i].flags & 32) /* Qz */
		{
			animatedOrient.z = animFrameData[jointInfos[i].startIndex + j];
			++j;
		}

		// Compute orient quaternion's w value:
		animatedOrient.ComputeW();

		/* NOTE: We assume that this joint's parent has
		already been calculated, i.e. joint's ID should
		never be smaller than its parent ID. */
		DoomMD5Joint * thisJoint = &skelFrame[i];

		int parent = jointInfos[i].parent;
		thisJoint->parent = parent;
		strcpy(thisJoint->name, jointInfos[i].name);

		// Has parent ?
		if (thisJoint->parent < 0)
		{
			thisJoint->pos = animatedPos;
			thisJoint->orient = animatedOrient;
		}
		else
		{
			DoomMD5Joint * parentJoint = &skelFrame[parent];
			Vec3 rpos; // Rotated position.

			// Add positions:
			parentJoint->orient.RotatePoint(animatedPos, rpos);
			thisJoint->pos.x = rpos.x + parentJoint->pos.x;
			thisJoint->pos.y = rpos.y + parentJoint->pos.y;
			thisJoint->pos.z = rpos.z + parentJoint->pos.z;

			// Concatenate rotations:
			thisJoint->orient = (parentJoint->orient * animatedOrient);
			thisJoint->orient.Normalize();
		}
	}
}

int DoomMD5Anim::GetNumFrames(void) const
{
	return (numFrames);
}

int DoomMD5Anim::GetNumJoints(void) const
{
	return (numJoints);
}

int DoomMD5Anim::GetFrameRate(void) const
{
	return (frameRate);
}

DoomMD5Joint ** DoomMD5Anim::GetFrames(void) const
{
	return (skelFrames);
}

BoundingBox * DoomMD5Anim::GetFramesBoundingBoxes(void) const
{
	return (bboxes);
}

float DoomMD5Anim::GetPlaybackTime(void) const
{
	return (static_cast<float>(numFrames) / static_cast<float>(frameRate));
}

unsigned long DoomMD5Anim::AddRef(void) const
{
	return (++refCount);
}

unsigned long DoomMD5Anim::Release(void) const
{
	if (--refCount == 0)
	{
		// Time to die...
		delete this;
		return (0);
	}

	return (refCount);
}

unsigned long DoomMD5Anim::ReferenceCount(void) const
{
	return (refCount);
}

// =========================================================
// DoomMD5Model Class Implementation
// =========================================================

// Shared data arrays //
unsigned int * DoomMD5Model::indexArray = 0;
int DoomMD5Model::numIndices = 0;
Vec3 * DoomMD5Model::vertexArray = 0;
int DoomMD5Model::numVertices = 0;
Vec2 * DoomMD5Model::texCoordArray = 0;

DoomMD5Model::DoomMD5Model(MemoryBuffer * memory)
: ReferenceCountable(), baseSkel(0), meshes(0), numJoints(0), numMeshes(0), animMap()
{
	// Load an MD5 model from a memory buffer, just as if it was a file.

	char buff[1024];
	int version, currMesh = 0;

	// Validation of the memory buffer is done outside.

	while (!memory->EndOfStream())
	{
		// Read whole line:
		memory->ReadString(buff, sizeof(buff));

		if (sscanf(buff, "MD5Version %d", &version) == 1)
		{
			if (version != 10)
			{
				// Bad version!
				LOG_ERROR("Bad model version!");
				return;
			}
		}
		else if (sscanf(buff, "numJoints %d", &numJoints) == 1)
		{
			if (numJoints > 0)
			{
				// Allocate memory for base skeleton joints:
				baseSkel = new DoomMD5Joint[numJoints];
				memset(baseSkel, 0, sizeof(DoomMD5Joint) * numJoints);
			}
		}
		else if (sscanf(buff, "numMeshes %d", &numMeshes) == 1)
		{
			if (numMeshes > 0)
			{
				// Allocate memory for meshes:
				meshes = new DoomMD5Mesh[numMeshes];
				memset(meshes, 0, sizeof(DoomMD5Mesh) * numMeshes);
			}
		}
		else if (strncmp(buff, "joints {", 8) == 0)
		{
			// Read each joint:
			for (int i = 0; i < numJoints; ++i)
			{
				DoomMD5Joint * joint = &baseSkel[i];

				// Read whole line:
				memory->ReadString(buff, sizeof(buff));

				if (sscanf (buff, "%s %d ( %f %f %f ) ( %f %f %f )",
					joint->name, &joint->parent, &joint->pos.x, &joint->pos.y, &joint->pos.z,
					&joint->orient.x, &joint->orient.y, &joint->orient.z) == 8)
				{
					// Compute the w component of the quaternion
					joint->orient.ComputeW();
				}
			}
		}
		else if (strncmp(buff, "mesh {", 6) == 0)
		{
			DoomMD5Mesh * mesh = &meshes[currMesh];

			int vertIndex = 0, triIndex = 0, weightIndex = 0;

			int idata[3];
			float fdata[4];

			while ((buff[0] != '}') && !memory->EndOfStream())
			{
				// Read whole line:
				memory->ReadString(buff, sizeof(buff));

				if (strstr(buff, "shader "))
				{
					int quote = 0, j = 0;

					// Copy the shader name whithout the quote marks:
					for (int i = 0; i < sizeof(buff) && (quote < 2); ++i)
					{
						if (buff[i] == '\"')
						{
							++quote;
						}

						if ((quote == 1) && (buff[i] != '\"'))
						{
							mesh->shader[j] = buff[i];
							++j;
						}
					}
				}
				else if (sscanf(buff, " numverts %d", &mesh->numVerts) == 1)
				{
					if (mesh->numVerts > 0)
					{
						// Allocate memory for the vertices:
						mesh->vertices = new DoomMD5Vertex[mesh->numVerts];
					}

					// We will allocate the shared vertex array with the largest size required by the application.
					if (mesh->numVerts > numVertices)
					{
						numVertices = mesh->numVerts;
					}
				}
				else if (sscanf(buff, " numtris %d", &mesh->numTris) == 1)
				{
					if (mesh->numTris > 0)
					{
						// Allocate memory for the triangles:
						mesh->triangles = new DoomMD5Triangle[mesh->numTris];
					}

					// We will allocate the shared index array with the largest size required by the application.
					if (mesh->numTris > numIndices)
					{
						numIndices = mesh->numTris;
					}
				}
				else if (sscanf(buff, " numweights %d", &mesh->numWeights) == 1)
				{
					if (mesh->numWeights > 0)
					{
						// Allocate memory for vertex the weights:
						mesh->weights = new DoomMD5Weight[mesh->numWeights];
					}
				}
				else if (sscanf(buff, " vert %d ( %f %f ) %d %d", &vertIndex, &fdata[0], &fdata[1], &idata[0], &idata[1]) == 5)
				{
					// Copy vertex data:

					// The vertical direction of the texture coordinates is the inverse of the standard OpenGL direction for the T coordinate.
					// So when loading a texture you'll have to flip it vertically or take
					// the oposite of the T texture coordinate for MD5 Mesh vertices (i.e. 1.0 - T).
					mesh->vertices[vertIndex].st[0] = fdata[0];
					mesh->vertices[vertIndex].st[1] = 1.0f - fdata[1];

					mesh->vertices[vertIndex].start = idata[0];
					mesh->vertices[vertIndex].count = idata[1];
				}
				else if (sscanf(buff, " tri %d %d %d %d", &triIndex, &idata[0], &idata[1], &idata[2]) == 4)
				{
					// Copy triangle data:

					mesh->triangles[triIndex].index[0] = idata[0];
					mesh->triangles[triIndex].index[1] = idata[1];
					mesh->triangles[triIndex].index[2] = idata[2];
				}
				else if (sscanf(buff, " weight %d %d %f ( %f %f %f )", &weightIndex, &idata[0], &fdata[3], &fdata[0], &fdata[1], &fdata[2]) == 6)
				{
					// Copy vertex data:

					mesh->weights[weightIndex].joint = idata[0];
					mesh->weights[weightIndex].bias  = fdata[3];

					mesh->weights[weightIndex].pos.x = fdata[0];
					mesh->weights[weightIndex].pos.y = fdata[1];
					mesh->weights[weightIndex].pos.z = fdata[2];
				}
			}

			currMesh++;
		}
	}

	// The model should be good to go by now...
}

DoomMD5Model::~DoomMD5Model(void)
{
	// Free resources allocated for the model:

	delete[] baseSkel;

	for (int i = 0; i < numMeshes; ++i)
	{
		// Free mesh data:
		delete[] meshes[i].vertices;
		delete[] meshes[i].triangles;
		delete[] meshes[i].weights;

		// Release the texture, if any:
		if (meshes[i].textureObject)
		{
			meshes[i].textureObject->Release();
		}
	}

	delete[] meshes;

	// Release animations:
	AnimationMap::const_iterator it = animMap.begin();
	AnimationMap::const_iterator end = animMap.end();

	while (it != end)
	{
		(*it).second->Release();
		++it;
	}

	animMap.clear();
}

void DoomMD5Model::AllocSharedArrays(void)
{
	vertexArray = new Vec3[numVertices];
	texCoordArray = new Vec2[numVertices];
	indexArray = new unsigned int[numIndices * 3];
}

void DoomMD5Model::FreeSharedArrays(void)
{
	delete[] vertexArray;
	delete[] texCoordArray;
	delete[] indexArray;

	vertexArray   = 0;
	texCoordArray = 0;
	indexArray    = 0;
}

bool DoomMD5Model::RegisterAnimation(DoomMD5Anim * newAnim, const std::string & animName)
{
	std::pair<AnimationMap::const_iterator, bool> res;

	// Register the animation as a new entry:
	res = animMap.insert(AnimationMap::value_type(animName, newAnim));

	if (res.second) // Check result
	{
		newAnim->AddRef();
		return (true);
	}

	return (false);
}

void DoomMD5Model::UnregisterAnimation(const std::string & animName)
{
	AnimationMap::const_iterator it = animMap.find(animName);

	if (it != animMap.end())
	{
		(*it).second->Release();
		animMap.erase(it);
	}
}

int DoomMD5Model::Animate(DoomMD5Anim * anim, float elapsedTime)
{
	int loopComplete = 0;

	if (anim != 0)
	{
		const int maxFrames = (anim->numFrames - 1);
		anim->lastTime += elapsedTime;

		// Move to next frame:
		if (anim->lastTime >= anim->maxTime)
		{
			anim->currFrame++;
			anim->nextFrame++;
			anim->lastTime = 0;

			if (anim->currFrame > maxFrames)
			{
				anim->currFrame = 0;
				loopComplete = 1;
			}

			if (anim->nextFrame > maxFrames)
			{
				anim->nextFrame = 0;
			}
		}

		// Interpolate skeletons between two frames:
		InterpolateSkeletons(anim->skelFrames[anim->currFrame], anim->skelFrames[anim->nextFrame],
		anim->numJoints, (anim->lastTime * anim->frameRate), baseSkel);
	}

	return (loopComplete);
}

int DoomMD5Model::GetNumMeshes(void) const
{
	return (numMeshes);
}

DoomMD5Mesh * DoomMD5Model::GetMesh(int which) const
{
	return (&meshes[which]);
}

int DoomMD5Model::GetNumJoints(void) const
{
	return (numJoints);
}

DoomMD5Joint * DoomMD5Model::GetJoint(int which) const
{
	return (&baseSkel[which]);
}

int DoomMD5Model::GetNumAnimations(void) const
{
	return (animMap.size());
}

DoomMD5Anim * DoomMD5Model::GetAnimation(const std::string & animName) const
{
	DoomMD5Anim * anim = 0;
	AnimationMap::const_iterator it = animMap.find(animName);

	if (it != animMap.end())
	{
		anim = (*it).second;
		anim->AddRef(); // Release() must be called outside latter on...
	}

	return (anim);
}

void DoomMD5Model::PrepareMesh(const DoomMD5Mesh * mesh, const DoomMD5Joint * skeleton)
{
	// Fill up the shared array for this mesh:

	int i, j, k;

	// Setup vertex indices:
	for (k = 0, i = 0; i < mesh->numTris; ++i)
	{
		for (j = 0; j < 3; ++j, ++k)
		{
			indexArray[k] = mesh->triangles[i].index[j];
		}
	}

	// Setup vertices:
	for (i = 0; i < mesh->numVerts; ++i)
	{
		Vec3 finalVertex(0,0,0);

		// Calculate final vertex to draw with weights:
		for (j = 0; j < mesh->vertices[i].count; ++j)
		{
			const struct DoomMD5Weight * weight = &mesh->weights[mesh->vertices[i].start + j];
			const struct DoomMD5Joint * joint = &skeleton[weight->joint];

			// Calculate transformed vertex for this weight:
			Vec3 wv;
			joint->orient.RotatePoint(weight->pos, wv);

			// The sum of all weight->bias should be 1.0 !
			finalVertex.x += (joint->pos.x + wv.x) * weight->bias;
			finalVertex.y += (joint->pos.y + wv.y) * weight->bias;
			finalVertex.z += (joint->pos.z + wv.z) * weight->bias;
		}

		texCoordArray[i].x = mesh->vertices[i].st[0];
		texCoordArray[i].y = mesh->vertices[i].st[1];

		vertexArray[i].x = finalVertex.x;
		vertexArray[i].y = finalVertex.y;
		vertexArray[i].z = finalVertex.z;
	}
}

void DoomMD5Model::InterpolateSkeletons(const DoomMD5Joint * skelA, const DoomMD5Joint * skelB,
										int numberOfJoints, float interp, DoomMD5Joint * outSkel)
{
	for (int i = 0; i < numberOfJoints; ++i)
	{
		// Copy parent index:
		outSkel[i].parent = skelA[i].parent;

		// Linear interpolation for position:
		outSkel[i].pos.x = skelA[i].pos.x + interp * (skelB[i].pos.x - skelA[i].pos.x);
		outSkel[i].pos.y = skelA[i].pos.y + interp * (skelB[i].pos.y - skelA[i].pos.y);
		outSkel[i].pos.z = skelA[i].pos.z + interp * (skelB[i].pos.z - skelA[i].pos.z);

		// Spherical linear interpolation for orientation:
		outSkel[i].orient = skelA[i].orient.Slerp(skelB[i].orient, interp);
	}
}

unsigned long DoomMD5Model::AddRef(void) const
{
	return (++refCount);
}

unsigned long DoomMD5Model::Release(void) const
{
	if (--refCount == 0)
	{
		// Time to die...
		delete this;
		return (0);
	}

	return (refCount);
}

unsigned long DoomMD5Model::ReferenceCount(void) const
{
	return (refCount);
}

// =========================================================
// DoomMD5Factory Class Implementation
// =========================================================

DoomMD5Anim * DoomMD5Factory::CreateAnimFromFile(const std::string & fileName)
{
	DoomMD5Anim * anim = 0;

	if (!fileName.empty())
	{
		MemoryBuffer * memory = MemoryBuffer::CreateFromFileData(fileName);

		if (memory != 0)
		{
			anim = CreateAnimFromMemory(memory, fileName);
			memory->Release();
		}
		else
		{
			LOG_ERROR("Unable to load file \"" << fileName << "\" into memory!");
		}
	}

	return (anim);
}

DoomMD5Anim * DoomMD5Factory::CreateAnimFromMemory(MemoryBuffer * memory, const std::string & originalFile)
{
	DoomMD5Anim * anim = 0;

	if (memory != 0)
	{
		try {

			anim = new DoomMD5Anim(memory);
			LOG_MSG("Loaded MD5 Anim from file: " << originalFile);
		}
		catch (...) {

			if (anim)
			{
				anim->Release();
				anim = 0;
			}
			LOG_ERROR("Failed to create MD5 Anim! Exception ocurred...");
		}
	}

	return (anim);
}

DoomMD5Model * DoomMD5Factory::CreateModelFromFile(const std::string & fileName)
{
	DoomMD5Model * mdl = 0;

	if (!fileName.empty())
	{
		MemoryBuffer * memory = MemoryBuffer::CreateFromFileData(fileName);

		if (memory != 0)
		{
			mdl = CreateModelFromMemory(memory, fileName);
			memory->Release();
		}
		else
		{
			LOG_ERROR("Unable to load file \"" << fileName << "\" into memory!");
		}
	}

	return (mdl);
}

DoomMD5Model * DoomMD5Factory::CreateModelFromMemory(MemoryBuffer * memory, const std::string & originalFile)
{
	DoomMD5Model * mdl = 0;

	if (memory != 0)
	{
		try {

			mdl = new DoomMD5Model(memory);
			LOG_MSG("Loaded MD5 Model from file: " << originalFile);
		}
		catch (...) {

			if (mdl)
			{
				mdl->Release();
				mdl = 0;
			}
			LOG_ERROR("Failed to create MD5 Model! Exception ocurred...");
		}
	}

	return (mdl);
}